/*
	setup_GDT_64.c

	contains functionality for setting up the long mode GDT and associated sub-tables (TSSs)

	Author: Aidan Goddard
*/

#include "../kernel/headers/types.h"
#include "headers/printf.h"

typedef struct GDT64 GDT64;
struct __attribute__((__packed__)) GDT64
{
	uint16_t seg_limit_low;
	uint16_t seg_base_low;
	uint8_t seg_base_mid;
	uint8_t accessed : 1;
	uint8_t readable_writable : 1;
	uint8_t conforming_expand_down : 1;
	uint8_t reserved_1 : 2;
	uint8_t DPL : 2;
	uint8_t present : 1;
	uint8_t seg_limit_high : 4;
	uint8_t AVL : 1;
	uint8_t is_64_reserved : 1;
	uint8_t default_operand_size : 1;
	uint8_t granuality: 1;
	uint8_t seg_base_high;
};

typedef struct GDTR GDTR;
struct __attribute__((__packed__))GDTR
{
	uint16_t gdt_limit;
	uint64_t gdt_base;
};

typedef struct TSSDESC TSSDESC;
struct __attribute__((__packed__)) TSSDESC
{
	uint16_t seg_limit_low;
	uint16_t seg_base_low;
	uint8_t seg_base_mid;
	uint8_t type : 4;
	uint8_t reserved_1 : 1;
	uint8_t DPL : 2;
	uint8_t present : 1;
	uint8_t seg_limit_mid : 4;
	uint8_t AVL : 1;
	uint8_t reserved_2 : 2;
	uint8_t granuality : 1;
	uint8_t seg_base_high;
	uint32_t seg_base_extended;
	uint32_t reserved_3;
};

typedef struct TSSSTRUCT TSSSTRUCT;
struct __attribute__((__packed__,aligned(128))) TSSSTRUCT
{
	uint32_t reserved_0;
	uint64_t RSP0;
	uint64_t RSP1;
	uint64_t RSP2;
	uint64_t reserved_1;
	uint64_t IST1;
	uint64_t IST2;
	uint64_t IST3;
	uint64_t IST4;
	uint64_t IST5;
	uint64_t IST6;
	uint64_t IST7;
	uint64_t reserved_2;
	uint32_t reserved_3;
	uint32_t IO_bitmap_base;
};

/*
typedef struct
{
	uint32_t reserved;
	uint8_t reserved2;
	uint8_t p_dpl_c;
	uint8_t d_l;
	uint8_t reserved3;
} __attribute__((__packed__))GDT64;
*/

// function to setup the new GDT and register it + load TSS 0
void SetupGDT()
{

	// create 8 descriptors (NULL, k_code, NULL, 32_u_code, NULL, 64_u_code, data, NULL) TODO: double check these
	GDT64 *gdt = (GDT64*)(0x701000 + KOFFSET);

	/*
	gdt64[0].reserved = 0;
	gdt64[0].reserved2 = 0;
	gdt64[0].p_dpl_c = 0;
	gdt64[0].d_l = 0;
	gdt64[0].reserved3 = 0;

	// k_code
	gdt64[1].reserved = 0;
	gdt64[1].reserved2 = 0;
	gdt64[1].p_dpl_c = 0x98;
	gdt64[1].d_l = 0x20;
	gdt64[1].reserved3 = 0;

	// data
	gdt64[2].reserved = 0;
	gdt64[2].reserved2 = 0;
	gdt64[2].p_dpl_c = 0x90;
	gdt64[2].d_l = 0;
	gdt64[2].reserved3 = 0;

	// u_code
	gdt64[3].reserved = 0;
	gdt64[3].reserved2 = 0;
	gdt64[3].p_dpl_c = 0xf8;
	gdt64[3].d_l = 0x20;
	gdt64[3].reserved3 = 0;
	*/

	// first null
	gdt[0].seg_limit_low = 0;
	gdt[0].seg_limit_high = 0;
	gdt[0].seg_base_low = 0;
	gdt[0].seg_base_mid = 0;
	gdt[0].seg_base_high = 0;
	gdt[0].granuality = 0;
	gdt[0].is_64_reserved = 0;
	gdt[0].default_operand_size = 0;
	gdt[0].DPL = 0;
	gdt[0].present = 0;
	gdt[0].readable_writable = 0;
	gdt[0].accessed = 0;
	gdt[0].conforming_expand_down = 0;
	gdt[0].reserved_1 = 0;
	gdt[0].AVL = 0;

	// kernel code
	gdt[1].seg_limit_low = 0;
	gdt[1].seg_limit_high = 0;
	gdt[1].seg_base_low = 0;
	gdt[1].seg_base_mid = 0;
	gdt[1].seg_base_high = 0;
	gdt[1].granuality = 0;
	gdt[1].is_64_reserved = 1;
	gdt[1].default_operand_size = 0;
	gdt[1].DPL = 0;
	gdt[1].present = 1;
	gdt[1].readable_writable = 0;
	gdt[1].accessed = 0;
	gdt[1].conforming_expand_down = 0;
	gdt[1].reserved_1 = 3;
	gdt[1].AVL = 0;

	// second null
	gdt[2].seg_limit_low = 0;
	gdt[2].seg_limit_high = 0;
	gdt[2].seg_base_low = 0;
	gdt[2].seg_base_mid = 0;
	gdt[2].seg_base_high = 0;
	gdt[2].granuality = 0;
	gdt[2].is_64_reserved = 0;
	gdt[2].default_operand_size = 0;
	gdt[2].DPL = 0;
	gdt[2].present = 0;
	gdt[2].readable_writable = 0;
	gdt[2].accessed = 0;
	gdt[2].conforming_expand_down = 0;
	gdt[2].reserved_1 = 0;
	gdt[2].AVL = 0;

	// 32 bit user code
	gdt[3].seg_limit_low = 0xffff;
	gdt[3].seg_limit_high = 0xf;
	gdt[3].seg_base_low = 0;
	gdt[3].seg_base_mid = 0;
	gdt[3].seg_base_high = 0;
	gdt[3].granuality = 1;
	gdt[3].is_64_reserved = 0;
	gdt[3].default_operand_size = 1;
	gdt[3].DPL = 3;
	gdt[3].present = 1;
	gdt[3].readable_writable = 1;
	gdt[3].accessed = 0;
	gdt[3].conforming_expand_down = 0;
	gdt[3].reserved_1 = 3;
	gdt[3].AVL = 0;

	// third null
	gdt[4].seg_limit_low = 0;
	gdt[4].seg_limit_high = 0;
	gdt[4].seg_base_low = 0;
	gdt[4].seg_base_mid = 0;
	gdt[4].seg_base_high = 0;
	gdt[4].granuality = 0;
	gdt[4].is_64_reserved = 0;
	gdt[4].default_operand_size = 0;
	gdt[4].DPL = 0;
	gdt[4].present = 0;
	gdt[4].readable_writable = 0;
	gdt[4].accessed = 0;
	gdt[4].conforming_expand_down = 0;
	gdt[4].reserved_1 = 0;
	gdt[4].AVL = 0;

	// 64 bit user code
	gdt[5].seg_limit_low = 0;
	gdt[5].seg_limit_high = 0;
	gdt[5].seg_base_low = 0;
	gdt[5].seg_base_mid = 0;
	gdt[5].seg_base_high = 0;
	gdt[5].granuality = 0;
	gdt[5].is_64_reserved = 1;
	gdt[5].default_operand_size = 0;
	gdt[5].DPL = 3;
	gdt[5].present = 1;
	gdt[5].readable_writable = 0;
	gdt[5].accessed = 0;
	gdt[5].conforming_expand_down = 0;
	gdt[5].reserved_1 = 3;
	gdt[5].AVL = 0;

	// user data
	gdt[6].seg_limit_low = 0xffff;
	gdt[6].seg_limit_high = 0xf;
	gdt[6].seg_base_low = 0;
	gdt[6].seg_base_mid = 0;
	gdt[6].seg_base_high = 0;
	gdt[6].granuality = 1;
	gdt[6].is_64_reserved = 0;
	gdt[6].default_operand_size = 1;
	gdt[6].DPL = 3;
	gdt[6].present = 1;
	gdt[6].readable_writable = 1;
	gdt[6].accessed = 0;
	gdt[6].conforming_expand_down = 0;
	gdt[6].reserved_1 = 2;
	gdt[6].AVL = 0;

	// fourth null
	gdt[7].seg_limit_low = 0;
	gdt[7].seg_limit_high = 0;
	gdt[7].seg_base_low = 0;
	gdt[7].seg_base_mid = 0;
	gdt[7].seg_base_high = 0;
	gdt[7].granuality = 0;
	gdt[7].is_64_reserved = 0;
	gdt[7].default_operand_size = 0;
	gdt[7].DPL = 0;
	gdt[7].present = 0;
	gdt[7].readable_writable = 0;
	gdt[7].accessed = 0;
	gdt[7].conforming_expand_down = 0;
	gdt[7].reserved_1 = 0;
	gdt[7].AVL = 0;


	// create 32 TSS descriptors - start from selector 0x40
	TSSDESC *tss = (TSSDESC*)(0x701040 + KOFFSET);

	// tss structures are at 0x100710000 with 128 bytes per structure
	int i;
	for(i = 0; i < 32; i++)
	{
		// fill in segment limit (104 bytes)
		tss[i].seg_limit_low = 104;
		tss[i].seg_limit_mid = 0;

		// fill in segment base
		// base = 0x00000001 00 71 0000 + (128 + i)
		tss[i].seg_base_low = 128 * i;	// [15:0]
		tss[i].seg_base_mid = 0x71;	// [23:16]
		tss[i].seg_base_high = 0x00;	// [31:24]
		tss[i].seg_base_extended = 1;	// [63:32]

		// fill in other attributes
		tss[i].type = 0x9;		// type = free 64 bit TSS
		tss[i].DPL = 0;			// DPL = 0
		tss[i].present = 1;		// segment is present in memory
		tss[i].AVL = 0;			// zero software available bit
		tss[i].granuality = 0;		// just use byte cound in limit

		// fill in zero values
		tss[i].reserved_1 = 0;
		tss[i].reserved_2 = 0;
		tss[i].reserved_3 = 0;
	}

	// setup tss structures
	// UPDATE: now uses ISTs. each IST can reference 7 ISR stacks for the processor to use.
	// stacks are at 0x100800000 -> 0x100a00000 (kernel + 8MB -> kernel + 10MB)

	// get pointer to TSS structures start
	TSSSTRUCT *tss_struct = (TSSSTRUCT*)(0x710000 + KOFFSET);

	// setup each of the 32 structures
	for(i = 0; i < 32; i++)
	{
		// fill in zero-values
		tss_struct[i].reserved_0 = 0;
		tss_struct[i].reserved_1 = 0;
		tss_struct[i].reserved_2 = 0;
		tss_struct[i].reserved_3 = 0;
		tss_struct[i].IO_bitmap_base = 0;
		tss_struct[i].RSP0 = 0;
		tss_struct[i].RSP1 = 0;
		tss_struct[i].RSP2 = 0;

		// get address of bottom of stack range for this TSS
		uint64_t stacks_bottom = (i * 0x10000) + (0x800000 + KOFFSET);

		// fill in IST values
		// skips the first stack section as that is reserved for syscall
		tss_struct[i].RSP0 = stacks_bottom + 0x2000;
		tss_struct[i].IST1 = stacks_bottom + 0x4000;
		tss_struct[i].IST2 = stacks_bottom + 0x6000;
		tss_struct[i].IST3 = stacks_bottom + 0x8000;
		tss_struct[i].IST4 = stacks_bottom + 0xa000;
		tss_struct[i].IST5 = stacks_bottom + 0xc000;
		tss_struct[i].IST6 = stacks_bottom + 0xe000;
		tss_struct[i].IST7 = stacks_bottom + 0x10000;
	}

	// load GDTR into processor
	GDTR gdtr;
	gdtr.gdt_limit = 4096;
	gdtr.gdt_base = (0x701000 + KOFFSET);
	__asm__ __volatile__("lgdt (%0)"::"p" (&gdtr));

	// load data segment (0x30). This doesn't change during normal operation.
	__asm__ __volatile("mov $0x30, %ax; mov %ax, %ds; mov %ax, %es");

	// load null segment into ss
	__asm__ __volatile("mov $0x0, %ax; mov %ax, %ss");

	// load TSS 0 into this processor
	uint16_t tss_selector = 0x40;
	__asm__ __volatile__("mov %0, %%ax; ltr %%ax"::"b" (tss_selector):"memory", "%rax");

	/*
	// how to get the CPU ID from its TSS number
	unsigned long int test_tr = 0;
    	__asm__ __volatile__("str %%rax; mov %%rax, %0":"=r"(test_tr)::"%rax", "memory");
    	test_tr = test_tr - 0x40;
    	test_tr = test_tr / 0x10;
	printf("\nCPU core: %u", test_tr);
	*/


	return;
}

















